// locutor
// (c) 2008-2009, Normen Müller, normen.mueller@gmail.com  
// $Id: Parser.scala 2481 2009-10-07 16:54:42Z nmueller $
package locutor.model.parsing.dep

import java.io.File
import java.net.URL
import java.text.MessageFormat.{format}

import scala.util.parsing.combinator._
import scala.util.parsing.combinator.syntactical._

import scaup.Views._
import scalaxx.xpath.{Expr => XPath}
import scalaxx.xpath.XPath.{parse}

import locutor.model.G

private [dep] case class Node(id: String, t: String, url: URL, xpath: XPath, as: List[(String, String)])
private [dep] case class Edge(src: Node, t: String, tgt: Node, as: List[(String, String)])

/** 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.2 */
private [dep] class Parser extends StdTokenParsers with ImplicitConversions {
  /* Fill in abstract defs*/
  type Tokens = Lexer
  val lexical: Tokens = new Lexer
  
  /* Configure lexical parsing */                                 
  lexical.reserved ++= List("graph", "for", "at", "model")
  lexical.delimiters ++= List("{", "}", "(", ")", "-:", ":", "=", ";", ",", "->")
  
  /* Define the grammar */
  def dep = ("graph" ~> ident) ~ ("for" ~> doc) ~ ("at" ~> rev) ~ ("{" ~> stmts <~ "}") ^^ {
    case name ~ document ~ revision ~ ((ms, (ns, es))) =>
      val g = new G(name, document, revision)
      
      //g.æ("graph")(Map("M" -> ))
      
      for(Node(id, t, url, xpath, as) <- ns.values)                
        g.η(id, t, url, xpath, Map() ++ as)
      
      for(Edge(src, t, tgt, as) <- es) (g(src.id), g(tgt.id)) match {
        case (Some(x), Some(y)) => (x connectTo y)(t, Map() ++ as)
        case _                  => error("Fatal DEP parsing error.")
      }
      
      g
  }
  
  def stmts = model_stmts ~ (node_stmts >> edge_stmts) ^^ {
    case ms ~ nes => (ms, nes)
  }
  
  def model_stmts = ("model" ~> models) <~ opt(";")
  
  def models = (string ^^ {case ms => (Set.empty[File] /: ms.split(",\\s*"))(_ + _.toFile)}) ^? (
    {case ms if ms forall (_ exists) => ms},
    {case _ => "Document models may not be null."}
  )
  
  def node_stmts = (( ((nid <~ ":") ~ tid) >> node_attrs <~ opt(";") )*) ^^ {
    case ns => (Map.empty[String, Node] /: ns)((m, n) => n match {
      case (id, t, ((url, expr, as))) => m get id match {
        case Some(_) => throw new DepError(format("Identifier ''{0}'' already used", id))
        case None    => m + {(id, Node(id, t, url.toURL, parse(expr), as))}
      }
    })
  } 
  
  def node_attrs = (id: String, t: String) => "(" ~> ((attrp <~ opt(","))*) <~ ")" ^^ {
    case as => (as \? {case ("url", _) => true}, as \? {case ("xpath", _) => true}) match {
      case (Some(u@("url", url)), Some(x@("xpath", xpath))) => (id, t, (url, xpath, as - u - x))
      case _ => throw DepError(format("In ''{0}:{1}'' URL/ XPath is missing.", id, t))
    }      
  }
  
  def edge_stmts = (V: Map[String, Node]) =>
    (((nid <~ "-:") ~ tid ~ opt(edge_attrs) ~ ("->" ~> nid) <~ opt(";"))*) ^^ {
      case es => (V, ((Nil: List[Edge]) /: es)((l, e) => l ::: List(e match {
        case snid ~ tid ~ as ~ tnid =>
          Edge(V.getOrElse(snid, throw new DepError(format("Node with identifier ''{0}'' not yet defined.", snid))),
               tid,
               V.getOrElse(tnid, throw new DepError(format("Node with identifier ''{0}'' not yet defined.", tnid))),
               as.getOrElse(Nil))                                                            
      	})))
    }
  
  def edge_attrs = "(" ~> ((attrp <~ opt(","))*) <~ ")"
  
  def attrp = key ~ "=" ~ value ^^ {
    case key ~ _ ~ value => (key.toString, value.toString)
  }
  
  def doc     = string ^^ {case x /*if x.toFile exists*/ => x.toFile}
  def rev     = numeric
  def nid     = ident | string
  def tid     = ident
  def key     = string | ident | numeric
  def value   = string | ident | numeric  
  def string  = accept("string",  {case lexical.StringLit(n) => n})
  def numeric = accept("numeric", {case lexical.NumericLit(n) => n.toInt})
}
